/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File KeyImpl.java
 * Created on Jul 20, 2006
 *
 */
package il.ac.biu.cs.grossmm.api.keys;

/**
 * An implementation of Key with no more than 4 entries.
 * Used for memory and speed optimization. For larger keys
 * ArrayKey should be used.
 */
public final class SmallKey extends KeyBase implements Key {
    private KeyEntry entry0, entry1, entry2, entry3;
    private final Object value;
    private int size;
    private int hashCode;
    
    /**
     * Creates a new key with no subkeys and a null value.
     *
     */
    public SmallKey() {
        this(null);
    }
    
    /**
     * Creates a new key with no subkeys and a given value.
     * @param value value of the key
     */
    public SmallKey(Object value) {
        this.value = value;
        
        recalcHashCode();
    }
    
    /**
     * Creates a new key with a given value and subkeys (no more than 4).
     * @param value the key value
     * @param entries subkey entries (no more than 4)
     */
    public SmallKey(Object value, KeyEntry[] entries) {
        this.value = value;
        this.size = entries == null ? 0 : entries.length;
        
        switch( size = entries.length ) {
        case 4:
        	entry3 = entries[3];
        case 3:
        	entry2 = entries[2];
        case 2:
        	entry1 = entries[1];
        case 1:
        	entry0 = entries[0];
        case 0:
        	break;
        default:
        	throw new IllegalArgumentException("Attemp to create key with >4 entries");
        }
        
        recalcHashCode();
    }

    private void recalcHashCode() {
        if( value == null )
            hashCode = 0;
        else
            hashCode = value.hashCode();
        
        switch( size ) {
        case 4:
        	hashCode +=entry3.getAttribute().hashCode();
        	hashCode += entry3.getKey().hashCode();
        case 3:
        	hashCode +=entry2.getAttribute().hashCode();
        	hashCode += entry2.getKey().hashCode();
        case 2:
        	hashCode +=entry1.getAttribute().hashCode();
        	hashCode += entry1.getKey().hashCode();
        case 1:
        	hashCode +=entry0.getAttribute().hashCode();
        	hashCode += entry0.getKey().hashCode();
        case 0:
        	break;
        }
    }
    
    public KeyEntry entry(int i) {
        if( i>size-1 || i<0 )
        	throw new IndexOutOfBoundsException("No entry "+ i);
        
        switch( i ) {
        case 0:
        	return entry0;
        case 1:
        	return entry1;
        case 2:
        	return entry2;
        case 3:
        	return entry3;
        }
        
        throw new Error("Unreachable point");
    }

    public Key subkey(Attribute a) {
    	switch( size  ) {
        case 4:
        	if( entry3.getAttribute().equals(a) )
        		return entry3.getKey();
        case 3:
        	if( entry2.getAttribute().equals(a) )
        		return entry2.getKey();
        case 2:
        	if( entry1.getAttribute().equals(a) )
        		return entry1.getKey();
        case 1:
        	if( entry0.getAttribute().equals(a) )
        		return entry0.getKey();
        case 0:
        	break;
        default:
        	throw new Error("Unreachable point");
        }
        
        return null;
    }

    public Object value() {
        return value;
    }

    public int size() {
        return size;
    }

    @Override
    public boolean equals(Object obj) {
        if( ! (obj instanceof Key) )
            return false;
        
        Key k = (Key) obj;
        
        if( k == this )
            return true;
        
        if( value == null ) {
            if( k.value() != null)
                return false;
        } else if( ! value.equals(k.value()) )
            return false;
        
        if( size != k.size() )
            return false;
        
        Key key;
        
        switch( size ) {
        case 4:
        	key = k.subkey(entry3.getAttribute());
        	if( key == null || !entry3.getKey().equals(key))
        		return false;
        case 3:
        	key = k.subkey(entry2.getAttribute());
        	if( key == null || !entry2.getKey().equals(key))
        		return false;
        case 2:
        	key = k.subkey(entry1.getAttribute());
        	if( key == null || !entry1.getKey().equals(key))
        		return false;
        case 1:
        	key = k.subkey(entry0.getAttribute());
        	if( key == null || !entry0.getKey().equals(key))
        		return false;
        case 0:
        	break;
        default:
        	throw new IllegalArgumentException("Attemp to create key with >4 entries");
        }
        
        return true;
    }
    
    @Override
    public int hashCode() {
        return hashCode;
    }
    
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        if( size >0 )
        	sb.append("\n");
        
        sb.append(value);
        
        if( size > 0 ) {
            
            sb.append(" {\n");
            
            switch( size ) {
            case 4:
            	sb.append('\t');
            	sb.append(entry3.getAttribute());
            	sb.append(" = ");
            	appendKey(1, sb, entry3.getKey());
            	sb.append('\n');
            case 3:
            	sb.append('\t');
            	sb.append(entry2.getAttribute());
            	sb.append(" = ");
            	appendKey(1, sb, entry2.getKey());
            	sb.append('\n');
            case 2:
            	sb.append('\t');
            	sb.append(entry1.getAttribute());
            	sb.append(" = ");
            	appendKey(1, sb, entry1.getKey());
            	sb.append('\n');
            case 1:
            	sb.append('\t');
            	sb.append(entry0.getAttribute());
            	sb.append(" = ");
            	appendKey(1, sb, entry0.getKey());
            	sb.append("\n}\n");
            case 0:
            	break;
            default:
            	throw new IllegalArgumentException("Attemp to create key with >4 entries");
            }
        }
        
        return sb.toString();
    }
    
    public String toString(int tabCount) {
        String tabs= "\t\t\t\t\t\t\t\t\t\t\t\t";
        if( tabCount < tabs.length() )
            tabs = tabs.substring(0, tabCount);
                        
        StringBuffer sb = new StringBuffer(value == null ? "null" : value.toString());
        
        if( size > 0 )
        	sb.append(" {\n");
        
        switch( size ) {
        case 4:
        	sb.append(tabs);
        	sb.append('\t');
        	sb.append(entry3.getAttribute());
        	sb.append(" = ");
        	appendKey(tabCount, sb, entry3.getKey());
        	sb.append('\n');
        case 3:
        	sb.append(tabs);
        	sb.append('\t');
        	sb.append(entry2.getAttribute());
        	sb.append(" = ");
        	appendKey(tabCount, sb, entry2.getKey());
        	sb.append('\n');
        case 2:
        	sb.append(tabs);
        	sb.append('\t');
        	sb.append(entry1.getAttribute());
        	sb.append(" = ");
        	appendKey(tabCount, sb, entry1.getKey());
        	sb.append('\n');
        case 1:
        	sb.append(tabs);
        	sb.append('\t');
        	sb.append(entry0.getAttribute());
        	sb.append(" = ");
        	appendKey(tabCount, sb, entry0.getKey());
        	sb.append("\n}\n");
        case 0:
        	break;
        default:
        	throw new IllegalArgumentException("Attemp to create key with >4 entries");
        }
        
        return sb.toString();
    }

	/**
	 * @param tabCount
	 * @param sb
	 * @param key
	 */
	private void appendKey(int tabCount, StringBuffer sb, Key key)
	{
		if( key instanceof SmallKey )
		       sb.append(((SmallKey) key).toString(tabCount+1));
		   else
		       sb.append(key.toString());
	}
}
